﻿Imports Microsoft.Win32
Imports System.IO
Imports System.ComponentModel

Public Class AddSnapshotView

    Private Shared SnapshotsLockObject As New Object
    Public Property Snapshots As New List(Of HeapSnapshot)

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.SnapshotName.Text = String.Format("snap-{0}", Now.TimeOfDay.TotalSeconds)
    End Sub

    Private Sub AddClipboard_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        If My.Computer.Clipboard.ContainsText Then
            Dim content As String = My.Computer.Clipboard.GetText()
            Me.SnapshotText.Text = content
            Dim parsed As HeapSnapshot = ParseCurrentContent(content)
            If Not parsed Is Nothing Then
                parsed.Timestamp = Now
                Snapshots.Add(parsed)
            End If

        Else
            MessageBox.Show("no text found on clipboard")
        End If
    End Sub

    Private Sub AddFile_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        Dim dialog As New OpenFileDialog
        With dialog
            .DefaultExt = ".txt"
            .Multiselect = True
            .Filter = "Log files (*.log, *.txt)|*.log;*.txt|All Files|*.*"
        End With
        If dialog.ShowDialog() Then
            Dim sourcePaths() As String = dialog.FileNames
            'order by timestamp
            For Each p As String In sourcePaths
                Dim loaded As HeapSnapshot = LoadFromFile(p)
                If Not loaded Is Nothing Then
                    Snapshots.Add(loaded)
                End If
            Next
        End If
    End Sub

    Private Function LoadFromFile(ByVal sourcePath As String) As HeapSnapshot
        Dim parsed As HeapSnapshot = Nothing
        Dim fs As FileStream = Nothing
        Dim reader As StreamReader = Nothing
        Try
            fs = File.Open(sourcePath, FileMode.Open, FileAccess.Read)
            reader = New StreamReader(fs)
            Dim content As String = reader.ReadToEnd()
            Me.SnapshotName.Text = Path.GetFileName(sourcePath)
            Me.SnapshotText.Text = content
            parsed = ParseCurrentContent(content)
            If Not parsed Is Nothing Then
                parsed.DumpFile = New DumpInfo() With {.FileName = Path.GetFileName(sourcePath),
                                                       .CreatedDate = File.GetCreationTime(sourcePath),
                                                       .Size = fs.Length}
                parsed.Name = Path.GetFileName(sourcePath)
                parsed.Timestamp = File.GetCreationTime(sourcePath)
            End If
        Catch ex As Exception
            MessageBox.Show("Error loading file: " & ex.Message)
        Finally
            If Not reader Is Nothing Then
                reader.Close()
            End If
            If Not fs Is Nothing Then
                fs.Dispose()
            End If
        End Try
        Return parsed
    End Function

    Private Function ParseCurrentContent(ByVal content As String) As HeapSnapshot
        Dim parsed As HeapSnapshot = HeapStatParser.Parse(content)
        If parsed Is Nothing Then
            MessageBox.Show("No snapshot parsed from text. Please verify content and try again")
            Return Nothing
        End If
        parsed.Memory = MemoryInfoParser.Parse(content)
        parsed.Name = Me.SnapshotName.Text
        Return parsed
    End Function

    Private Sub OkButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        Me.Close()
    End Sub

    Private Sub CancelButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        Me.Close()
    End Sub


    Private mPendingDumpImports As New List(Of String)

    Private Sub AddDump_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        'invoke windbg from command line

        'todo - to be configured by user:
        'windbg on path?
        'symbol path?
        'executable path?
        'log path?

        Dim dmpPaths() As String = GetDumpFilePaths()
        If dmpPaths Is Nothing OrElse dmpPaths.Count = 0 Then Exit Sub
        OkButton.IsEnabled = False
        For Each dmpPath As String In dmpPaths
            SyncLock SnapshotsLockObject
                mPendingDumpImports.Add(dmpPath)
            End SyncLock
            ExtractHeapStat(dmpPath)
        Next

    End Sub

    Private Sub ExtractHeapStat(ByVal dmpPath As String)
        Dim runGuid As Guid = Guid.NewGuid()
        Dim logPath As String = String.Format("heap-{0}.log", runGuid)
        Dim scriptPath As String = System.IO.Path.Combine(Environment.CurrentDirectory, "scripts\autoDbgHeap.txt")
        Dim sosArg As String = System.IO.Path.Combine(Environment.CurrentDirectory, "extensions\net4.0")
        Dim scriptArg As String = String.Format("""$$>a<{0} {1}""", scriptPath, sosArg)
        Dim worker As New BackgroundWorker
        AddHandler worker.DoWork, AddressOf RunWindbgHeapStat
        AddHandler worker.RunWorkerCompleted, AddressOf OnWindbgHeapStatCompleted
        Dim args As New HeapStatDoWorkArgs() With {.dmpPath = dmpPath, .logPath = logPath, .scriptPath = scriptArg}
        worker.RunWorkerAsync(args)

    End Sub

    Private Class HeapStatDoWorkArgs
        Public Property dmpPath As String
        Public Property logPath As String
        Public Property scriptPath As String
    End Class

    Private Sub OnWindbgHeapStatCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
        If e.Cancelled Then Exit Sub
        If Not e.Error Is Nothing Then
            MessageBox.Show(e.Error.Message, "Error running WinDbg", MessageBoxButton.OK, MessageBoxImage.Exclamation)
            Exit Sub
        End If

        Dim args As HeapStatDoWorkArgs = DirectCast(e.Result, HeapStatDoWorkArgs)
        Dim loaded As HeapSnapshot = LoadFromFile(args.logPath)
        'todo - make log file cleanup optional??
        File.Delete(args.logPath)
        If Not loaded Is Nothing Then
            loaded.Name = Path.GetFileName(args.dmpPath)
            loaded.DumpFile.FileName = Path.GetFileName(args.dmpPath)
            loaded.DumpFile.CreatedDate = File.GetCreationTime(args.dmpPath)
            Dim dmpFile As New FileInfo(args.dmpPath)
            loaded.DumpFile.Size = dmpFile.Length
            SyncLock SnapshotsLockObject
                Snapshots.Add(loaded)
                mPendingDumpImports.Remove(args.dmpPath)
                If mPendingDumpImports.Count = 0 Then
                    MessageBox.Show("Imports complete")
                    OkButton.IsEnabled = True
                End If
            End SyncLock
        End If

    End Sub


    Private Sub RunWindbgHeapStat(ByVal sender As Object, ByVal e As DoWorkEventArgs)
        Dim args As HeapStatDoWorkArgs = DirectCast(e.Argument, HeapStatDoWorkArgs)

        Dim symPath As String = String.Format("SRV*{0}*http://msdl.microsoft.com/download/symbols", My.Settings.SymbolFolderPath)
        Dim procInfo As New ProcessStartInfo(Path.Combine(My.Settings.DbgTools_x86_FolderPath, "cdb.exe"))
        Dim bldArgs As New System.Text.StringBuilder()
        With bldArgs
            .AppendFormat("-z {0} ", args.dmpPath)
            .AppendFormat("-logo {0} ", args.logPath)
            '.Append("-Q ")
            .AppendFormat("-y ""{0}"" ", symPath)
            '.AppendFormat("-srcpath ""{0}"" ", srcPath)
            .AppendFormat("-c {0}", args.scriptPath)
        End With
        With procInfo
            .Arguments = bldArgs.ToString
            .CreateNoWindow = True
            '.WindowStyle = ProcessWindowStyle.Hidden
            .RedirectStandardOutput = True
            .RedirectStandardError = True
            .UseShellExecute = False
        End With

        Dim proc As New Process()
        Try
            proc.StartInfo = procInfo
            proc.EnableRaisingEvents = True

            AddHandler proc.OutputDataReceived, AddressOf OnOutputRecieved
            AddHandler proc.ErrorDataReceived, AddressOf OnErrorOutputRecieved
            AddHandler proc.Exited, AddressOf ProcExitCleanupHandler
            proc.Start()
            proc.BeginOutputReadLine()
            proc.BeginErrorReadLine()
            proc.WaitForExit()
        Catch ex As Exception
            Throw New Exception(String.Format("Cannot start process: {0}.", Command), ex)
        Finally
            If Not proc Is Nothing Then
                proc.Dispose()
            End If
        End Try
        e.Result = args
    End Sub

    Private Sub OnOutputRecieved(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        If Not String.IsNullOrEmpty(e.Data) Then
            WriteToOutput(ConsoleColor.Blue, e.Data)
        End If
    End Sub

    Private Sub OnErrorOutputRecieved(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        If Not String.IsNullOrEmpty(e.Data) Then
            WriteToOutput(ConsoleColor.Red, e.Data)
        End If
    End Sub

    Public Sub WriteToOutput(ByVal color As ConsoleColor, ByVal ParamArray lines As String())
        If Me.SnapshotText.CheckAccess Then
            UIThread_WriteToOutput(color, lines)
        Else
            Me.Dispatcher.Invoke(New UpdateDisplayOnUIThreadDelegate(AddressOf UIThread_WriteToOutput), color, lines)
        End If
    End Sub

    Private Delegate Sub UpdateDisplayOnUIThreadDelegate(ByVal color As ConsoleColor, ByVal lines As String())
    Private Sub UIThread_WriteToOutput(ByVal color As ConsoleColor, ByVal ParamArray lines As String())
        For Each line As String In lines
            Me.SnapshotText.AppendText(line)
            Me.SnapshotText.AppendText(Environment.NewLine)
            Me.SnapshotText.ScrollToEnd()
        Next
    End Sub


    Private Sub ProcExitCleanupHandler(ByVal sender As Object, ByVal e As EventArgs)
        Dim proc As Process = DirectCast(sender, Process)
        proc.Dispose()
    End Sub

    Private Function GetDumpFilePaths() As String()

        Dim dialog As New OpenFileDialog
        With dialog
            .DefaultExt = ".dmp"
            .Multiselect = True
            .Filter = "Dump files (*.dmp)|*.dmp"
        End With
        If dialog.ShowDialog() Then
            Return dialog.FileNames
        End If
        Return Nothing
    End Function
End Class
